package jmine.tec.utils.collection.impl;

import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * {@link AbstractHashMultiMap} que usa um {@link LinkedHashMap}
 * 
 * @author takeshi
 * @param <K> o tipo da chave
 * @param <V> o tipo do valor
 */
public abstract class AbstractHashMultiMap<K, V> extends AbstractMultiMap<K, V> implements Serializable {

    private final Map<K, Collection<V>> backingMap = new LinkedHashMap<K, Collection<V>>();

    private int size;

    /**
     * Subclasses devem implementar este metodo fornecendo as colecoes que serao alimentadas no {@link #backingMap}
     * 
     * @return {@link Collection} de V
     */
    protected abstract Collection<V> newCollection();

    /**
     * {@inheritDoc}
     */
    @Override
    public void clear() {
        this.backingMap.clear();
        this.size = 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int size() {
        return this.size;
    }

    /**
     * {@inheritDoc}
     */
    public boolean containsKey(K key) {
        return this.backingMap.containsKey(key);
    }
    
    /**
     * {@inheritDoc}
     */
    public boolean add(K key, V value) {
        Collection<V> collection = this.backingMap.get(key);
        if (collection == null) {
            collection = this.newCollection();
            this.backingMap.put(key, collection);
        }
        if (collection.add(value)) {
            this.size++;
            return true;
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends V> get(K key) {
        return this.backingMap.get(key);
    }

    /**
     * {@inheritDoc}
     */
    public boolean remove(K key, V item) {
        Collection<V> collection = this.backingMap.get(key);
        if (collection == null) {
            return false;
        }
        boolean remove = collection.remove(item);
        if (remove) {
            this.size--;
        }
        return remove;
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends V> removeAll(K key) {
        Collection<V> col = this.backingMap.remove(key);
        if (col != null) {
            this.size -= col.size();
        }
        return col;
    }

    /**
     * {@inheritDoc}
     */
    public Map<K, Collection<V>> toPlainMap() {
        return new LinkedHashMap<K, Collection<V>>(this.backingMap);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return this.backingMap.toString();
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#hashCode()
     */

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((this.backingMap == null) ? 0 : this.backingMap.hashCode());
        result = prime * result + this.size;
        return result;
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (this.getClass() != obj.getClass()) {
            return false;
        }
        AbstractHashMultiMap other = (AbstractHashMultiMap) obj;
        return this.size == other.size && this.backingMap.equals(other.backingMap);
    }

}
